package healthylife.top.sync;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.IdUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import io.minio.*;
import io.minio.messages.Item;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @author maomao
 */
@Slf4j
@Data
public class GFSSyncClient implements FileSyncService {


    private SourceClient sourceClient;
    private TargetClient targetClient;

    public Map<String, FileInfo> sourceFilesMap;
    public Map<String, FileInfo> lastTransferMap;

    String tempLocalPath;
    String workspace;

    GFSSyncClient(SourceClient sourceClient, TargetClient targetClient, String tempLocalPath, String workspace) {
        this.sourceClient = sourceClient;
        this.targetClient = targetClient;
        this.tempLocalPath = tempLocalPath;
        this.workspace = workspace;
        initTargetClient();
    }

    /**
     * 初始化targetClient
     */
    public void initTargetClient() {
        try {
            targetClient.minioClient = MinioClient.builder()
                    .endpoint(targetClient.minioEndpoint)
                    .credentials(targetClient.minioAccessKey, targetClient.minioSecretKey)
                    .build();
            targetClient.minioClient.makeBucket(MakeBucketArgs.builder().bucket(targetClient.minioBucket).build());
        } catch (Exception e) {
            throw new RuntimeException("minio-client初始化失败:" + e.getMessage());
        }
    }

    @Data
    static class SourceClient {
        String host;
        /**
         * token 失效时间  默认 24小时  24*60*60*1000
         */
        long tokenTtl = 24 * 60 * 60 * 1000;
        String loginName;
        String integrationKey;
        //POST  /api/services/Org/UserLoginIntegrationByUserLoginName
        final String api_tokenUrl = host + "/api/services/Org/UserLoginIntegrationByUserLoginName ";
        //GET  /api/services/Folder/GetChildrenFolders?token=0032719f4f34c3f946859145b42101322c95&topFolderId=227333
        final String api_getSubFolders = host + "/api/services/Folder/GetChildrenFolders";
        //GET /api/services/File/GetChildFileListByFolderIdRecursive?token=0032719f4f34c3f946859145b42101322c95&folderId=227333
        final String api_fileListByFolderId = host + "/api/services/File/GetChildFileListByFolderIdRecursive";
        //GET /downLoad/DownLoadCheck?token=0032ecd1c1cc87f3459287ce2ec5881f8db8&fileIds=17857380&r=1112
        final String api_fileToken = host + "/downLoad/DownLoadCheck";
        //GET  /downLoad/index?token=0032ecd1c1cc87f3459287ce2ec5881f8db8&fileIds=17857380&regionHash=xxxxxxxxx
        final String api_downloadFile = host + "/downLoad/index";
        String rootFolderId;
        String rootFolderName;

        String sourceRootFolder;
    }

    @Data
    static class TargetClient {
        String minioEndpoint;
        String minioAccessKey;
        String minioSecretKey;
        String minioBucket;
        String minioDecryptBucket;
        MinioClient minioClient;
    }


    public String getConf() {
        return getWorkspace() + File.separator + "sync-gfs.conf";
    }

    public String getLog(String time) {
        if (time == null) {
            time = LocalDateTime.now().toString();
        }
        return getWorkspace() + File.separator + "sync-gfs-" + time + ".log";
    }

    @Override
    public String getSource() {
        return this.sourceClient.getRootFolderId();
    }

    @Override
    public String getTarget() {
        return this.targetClient.minioBucket;
    }


    @Override
    @SneakyThrows
    public List<FileInfo> sourceFiles() {
        Supplier<List<GFSFIleInfo>> supplier = () -> getFilesByFolderId(Lazy.of(this::getToken));
        Lazy<List<GFSFIleInfo>> listLazy = Lazy.of(supplier);
        Lazy<List<FileInfo>> lazyRes = Lazy.of(() -> convertGFSFIleInfoToFileInfo(listLazy));
        sourceFilesMap = lazyRes.get().stream().collect(Collectors.toMap(FileInfo::getFilePath, Function.identity()));
        return retryWhenFail(lazyRes, "获取source文件列表");
    }


    @SneakyThrows
    @Override
    public List<FileInfo> targetFiles() {
        Iterable<Result<Item>> results = targetClient.minioClient.listObjects(
                ListObjectsArgs.builder().bucket(targetClient.minioBucket).recursive(true).build());
        List<FileInfo> files = new ArrayList<>();
        for (Result<Item> result : results) {
            Item item = result.get();
            FileInfo fileInfo = new FileInfo();
            fileInfo.setFilePath(item.objectName());
            fileInfo.setDirectory(item.isDir());
            fileInfo.setLastModifiedTime(item.lastModified().toInstant().toEpochMilli());
            files.add(fileInfo);
        }
        return files;
    }

    @Override
    @SneakyThrows
    public int transfer(String source, String target) {
        //先下载到本地temp路径然后再传输到target
        String tempLocalFilePath = tempLocalPath + File.separator + source;
        Path localPath = Paths.get(tempLocalFilePath);
        // 确保本地目录层级存在
        Path parent = localPath.getParent();
        File file = localPath.toFile();
        if (!parent.toFile().exists()) {
            try {
                Files.createDirectories(parent);
                downloadAndUpload(source, tempLocalFilePath);
            } catch (Exception e) {
                throw new RuntimeException(source + "同步失败,code:" + 0 + "error:" + e.getMessage());
            }
            return 0;
        }

        if (file.isDirectory()) {
            return -1;
        }
        if (!sourceFilesMap.containsKey(source)) {
            return -1;
        }
        try {
            //获取来源端的 文件 修改时间
            long lastModifiedTime = sourceFilesMap.get(source).getLastModifiedTime();
            //判断是否需要下载
            if (needTransfer(source, lastModifiedTime)) {
                //下载文件到本地目录
                downloadAndUpload(source, tempLocalFilePath);
                return 1;
            }
        } catch (Exception e) {
            throw new RuntimeException(source + "同步失败,code:" + 1 + "error:" + e.getMessage());
        }
        return -1;
    }

    /**
     * @param source            相对路径
     * @param tempLocalFilePath 本地路径
     */
    @SneakyThrows
    private void downloadAndUpload(String source, String tempLocalFilePath) {
        downloadObj(source, tempLocalFilePath);
        Path path = Paths.get(tempLocalFilePath);
        File file = path.toFile();
        FileInputStream fileInputStream = new FileInputStream(file);
        targetClient.minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(targetClient.minioBucket)
                        //不能携带桶目录
                        .object(source)
                        .stream(fileInputStream, file.length(), -1)
                        .build());
        //todo 解密 上传到解密专用桶
//        targetClient.minioClient.putObject(
//                PutObjectArgs.builder()
//                        .bucket(targetClient.minioDecryptBucket)
//                        //不能携带桶目录
//                        .object(source)
//                        .stream(fileInputStream, file.length(), -1)
//                        .build());
    }

    @SneakyThrows
    public void downloadObj(String source, String localPath) {
        FileInfo fileInfo = sourceFilesMap.get(source);
        String fileId = fileInfo.getFileId();
        Lazy<String> tokenLazy = Lazy.of(this::getToken);
        Map<String, Object> request = new LinkedHashMap<>();
        request.put("token", tokenLazy.get(sourceClient.tokenTtl));
        request.put("fileIds", fileId);
        request.put("r", IdUtil.getSnowflake().nextIdStr());
        String regionHash;
        try {

            HttpResponse execute = HttpUtil.createGet(sourceClient.getApi_fileToken())
                    .body(JSONUtil.toJsonStr(request))
                    .execute();
            String body = execute.body();
            TypeReference<FileTokenResponse> typeReference = new TypeReference<FileTokenResponse>() {
            };
            FileTokenResponse bean = JSONUtil.toBean(body, typeReference, false);
            if (bean.getNResult() != 0) {
                throw new RuntimeException("TOKEN 获取错误:" + bean.getMsg());
            }
            regionHash = bean.getRegionHash();
        } catch (Exception e) {
            throw new RuntimeException("TOKEN 获取异常:" + e.getMessage());
        }
        //下载文件
        try {
            Path path = Paths.get(localPath);
            File file = path.toFile();
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            HttpUtil.download(sourceClient.getApi_downloadFile() + "?token=" + tokenLazy.get(getSourceClient().tokenTtl) + "&fileId=" + fileId + "&regionHash=" + regionHash, fileOutputStream, true);
        } catch (Exception e) {
            throw new RuntimeException("文件下载异常:" + e.getMessage());
        }
        System.out.println("文件下载完成，临时本地存储" + localPath);

    }

    @SneakyThrows
    @Override
    public void deleteFile(String filePath) {
        targetClient.minioClient.removeObject(RemoveObjectArgs.builder().bucket(targetClient.minioBucket).object(filePath).build());
    }

    @SneakyThrows
    public boolean needTransfer(String relativeFilePath, long currentFileLastModifiedTime) {
        if (lastTransferMap == null) {
            Path path = Paths.get(getConf());
            File file = path.toFile();
            if (!file.exists()) {
                log.warn("同步文件配置信息不存在");
                return true;
            }
            String json = IoUtil.read(Files.newInputStream(file.toPath()), Charset.defaultCharset());
            List<FileInfo> list = JSONUtil.toList(json, FileInfo.class);
            lastTransferMap = list.stream().collect(Collectors.toMap(FileInfo::getFilePath, Function.identity()));
        }
        if (lastTransferMap.containsKey(relativeFilePath)) {
            boolean b = lastTransferMap.get(relativeFilePath).getLastModifiedTime() != currentFileLastModifiedTime;
            if (b) {
                log.info("文件:{}有更新", relativeFilePath);
                return true;
            } else {
                return false;
            }
        }
        return true;

    }

    /**
     * token 获取
     *
     * @return token
     */
    public String getToken() {
        TokenRequest tokenRequest = new TokenRequest();
        tokenRequest.setIntegrationKey(sourceClient.getIntegrationKey());
        tokenRequest.setLoginName(sourceClient.getLoginName());
        try {

            HttpResponse execute = HttpUtil.createPost(sourceClient.getApi_tokenUrl())
                    .body(JSONUtil.toJsonStr(tokenRequest))
                    .execute();
            String body = execute.body();
            TypeReference<R<String>> typeReference = new TypeReference<R<String>>() {
            };
            R<String> bean = JSONUtil.toBean(body, typeReference, false);
            if (bean.getResult() != 0) {
                throw new RuntimeException("TOKEN 获取错误:" + bean.getMessage());
            }
            return bean.getData();
        } catch (Exception e) {
            throw new RuntimeException("TOKEN 获取异常:" + e.getMessage());
        }
    }

    public <T> T retryWhenFail(Lazy<T> supplier, String supplierName) {
        int maxRetries = 3;
        int attempts = 0;
        Exception lastException = null;
        while (attempts < maxRetries) {
            try {
                return supplier.get();
            } catch (Exception e) {
                lastException = e;
                log.warn("{}执行失败: {}, 尝试次数: {}", supplierName, e.getMessage(), attempts + 1);
                attempts++;
                if (attempts < maxRetries) {
                    try {
                        Thread.sleep(1000); // 等待1秒后重试
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        log.warn("线程中断: {}", ie.getMessage());
                    }
                }
            }
        }
        throw new RuntimeException(supplierName + "执行失败: " + lastException.getMessage(), lastException);
    }

    /**
     * 根据文件夹Id获取文件列表信息
     *
     * @param token tokenSupplier
     * @return 文件列表
     */
    public List<GFSFIleInfo> getFilesByFolderId(Lazy<String> token) {
        Map<String, Object> requestMap = new LinkedHashMap<>();
        requestMap.put("token", token.get(sourceClient.tokenTtl));
        requestMap.put("folderId", sourceClient.getRootFolderId());
        try {
            HttpResponse execute = HttpUtil.createGet(sourceClient.getApi_fileListByFolderId())
                    .form(requestMap)
                    .execute();
            String body = execute.body();
            TypeReference<R<List<GFSFIleInfo>>> typeReference = new TypeReference<R<List<GFSFIleInfo>>>() {
            };
            R<List<GFSFIleInfo>> bean = JSONUtil.toBean(body, typeReference, false);
            if (bean.getResult() != 0) {
                throw new RuntimeException("根据文件目录Id获取文件信息失败:" + bean.getMessage());
            }
            return bean.getData();
        } catch (Exception e) {
            throw new RuntimeException("根据文件目录Id获取文件信息异常:" + e.getMessage());
        }
    }

    public List<FileInfo> convertGFSFIleInfoToFileInfo(Lazy<List<GFSFIleInfo>> data) {
        List<FileInfo> list = new ArrayList<>();
        List<GFSFIleInfo> gfsfIleInfos = data.get();
        //处理filePath的路径 GFS的路径问题
        //1.不包含文件的名称 //2.路径为目录的id,需要替换为名称
        for (GFSFIleInfo gfsFIleInfo : gfsfIleInfos) {
            FileInfo fileInfo = new FileInfo();
            String filePath = gfsFIleInfo.getFilePath();
            String newFilePath = filePathConvert(filePath, gfsFIleInfo.getFileName());
            fileInfo.setFilePath(newFilePath);
            fileInfo.setSize(gfsFIleInfo.getFileTotalSize());
            fileInfo.setFileId(String.valueOf(gfsFIleInfo.getFileId()));
            LocalDateTime fileModifyTime = gfsFIleInfo.getFileModifyTime();
            fileInfo.setLastModifiedTime(Timestamp.valueOf(fileModifyTime).toInstant().getEpochSecond());
        }
        return list;
    }

    /**
     * @param filePathOrigin 形如 1\\805\\227333\\227336\\236469\\
     * @param filename       形如 javascript.pdf
     * @return ROOT\\805中文明\\22733中文名\\...\\javascript.pdf
     */
    public String filePathConvert(String filePathOrigin, String filename) {
        Map<String, String> filePathMap = new HashMap<>();
        String[] split = filePathOrigin.split("\\\\");
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < split.length; i++) {
            if (split[i].equals(sourceClient.rootFolderId)) {
                stringBuilder.append(sourceClient.rootFolderName);
            }
            if (i == split.length - 1) {
                stringBuilder.append(filename);
            } else {
                String currentPath = split[i];
                if (filePathMap.containsKey(currentPath)) {
                    stringBuilder.append(filePathMap.get(currentPath)).append(File.separator);
                } else {
                    String parentFoldId = getParentPath(filePathOrigin, currentPath);
                    Map<String, String> subFolderByFolderId = getSubFolderByFolderId(Lazy.of(this::getToken), parentFoldId);
                    filePathMap.putAll(subFolderByFolderId);
                    stringBuilder.append(filePathMap.get(currentPath)).append(File.separator);
                }
            }
        }
        return stringBuilder.toString();
    }

    /**
     * @param filePath        形如 1\\805\\227333\\227336\\236469\\
     * @param currentFilePath 当前路径 如 236469
     * @return 返回 227336
     */
    public String getParentPath(String filePath, String currentFilePath) {
        String[] split = filePath.split("\\\\");
        for (int i = 0; i < split.length; i++) {
            if (split[i].equals(currentFilePath)) {
                return split[i - 1];
            }
        }
        throw new RuntimeException("当前路径" + currentFilePath + "不存在于给的的路径目录" + filePath);
    }

    /**
     * 获取指定文件夹下的文件夹信息
     *
     * @param tokenSupplier token
     * @param foldId        文件夹id
     * @return 文件夹信息
     */
    public Map<String, String> getSubFolderByFolderId(Lazy<String> tokenSupplier, String foldId) {
        String token = tokenSupplier.get(sourceClient.tokenTtl);
        Map<String, Object> requestMap = new LinkedHashMap<>();
        requestMap.put("token", token);
        requestMap.put("topFolderId", foldId);
        try {
            HttpResponse execute = HttpUtil.createGet(sourceClient.getApi_getSubFolders()).form(requestMap).execute();
            String body = execute.body();
            TypeReference<R<List<FolderInfoResult>>> typeReference = new TypeReference<R<List<FolderInfoResult>>>() {
            };
            R<List<FolderInfoResult>> bean = JSONUtil.toBean(body, typeReference, false);
            if (bean.getResult() != 0) {
                throw new RuntimeException("根据文件目录Id获取文件信息失败:" + bean.getMessage());
            }
            return bean.getData().stream().collect(Collectors.toMap(e -> String.valueOf(e.getFolderId()), FolderInfoResult::getFolderName));
        } catch (Exception e) {
            throw new RuntimeException("根据文件目录Id获取文件信息异常:" + e.getMessage());
        }
    }

    @Data
    public static class FileTokenResponse {
        // 0 = success
        private Integer nResult;
        private String msg;
        private Integer RegionId;
        private Integer RegionType;
        // 下载令牌
        private String RegionHash;
        private String RegionUrl;
        private Integer ConversionState;

    }


    @Data
    public static class TokenRequest {
        private final String ipAddress = "127.0.0.1";
        //登录账号
        private String loginName;
        //系统密钥，申请系统账号时分配
        private String integrationKey;
    }

    @Data
    public static class R<T> {
        private T data;
        private String dataDescription;
        // 0 =  success
        private Integer result;
        private String message;
    }

    @Data
    public static class GFSFIleInfo {
        private Long FileId;
        private Long ParentFolderId;
        private String FileCurVerNumStr;
        private Long FileCurSize;
        private String FileCurCode;
        private String FileCurRemark;
        private String FileLastVerNumStr;
        private Long FileLastSize;
        private String FileLastCode;
        private Long FileLastVerId;
        private String FileLastRemark;
        private String FileGuid;
        private String FileName;
        private String FileExtName;
        private String FileLastVerExtName;
        private String FileLastVerName;
        private String FileContentType;
        private String FileRemark;
        private Long FileCurVerId;
        private String FilePath;
        private Long FileTotalSize;
        private Integer SecurityLevelId;
        private String SecurityLevelName;
        private Integer FileState;
        private Integer FileCurrentOperatorId;
        private LocalDateTime FileCreateTime;
        private Integer FileCreateOperator;
        private LocalDateTime FileModifyTime;
        private Integer FileModifyOperator;
        private Integer FileCreateType;
        private LocalDateTime FileArchiveTime;
        private Integer FileArchiveOperator;
        private Boolean IsDeleted;
        private LocalDateTime FileDeleteTime;
        private Integer FileDeleteOperator;
        private Boolean FileIsCascadeDelete;
        private Boolean FileCipherText;
        private String RelativePath;
        private String IncId;
        private LocalDateTime FileEffectiveTime;
        private LocalDateTime FileExpirationTime;
        private Map<String, Object> extInfo;
        private Integer FileType;
        private Integer FileOfflinePermission;
        private LocalDateTime FilePurgeTime;
        private Integer FilePurgeOperator;
        private Integer InstanceId;
        private Integer FileOwnerId;
        private Integer CurrentUserPerm;
        //示例数据
//                 "FileId": 17857380,
//                "ParentFolderId": 236469,
//                "FileCurVerNumStr": "1.0",
//                "FileCurSize": 538242,
//                "FileCurCode": "",
//                "FileCurRemark": "",
//                "FileLastVerNumStr": "1.0",
//                "FileLastSize": 538242,
//                "FileLastCode": "",
//                "FileLastVerId": 17860469,
//                "FileLastRemark": "",
//                "FileGuid": "51bc55db-2b22-4cde-a68c-21354fc0381a",
//                "FileName": "neo4j.docx",
//                "FileExtName": ".docx",
//                "FileLastVerExtName": ".docx",
//                "FileLastVerName": "neo4j.docx",
//                "FileContentType": "",
//                "FileRemark": "",
//                "FileCurVerId": 17860469,
//                "FilePath": "1\\805\\227333\\227336\\236469\\",
//                "FileTotalSize": 538242,
//                "SecurityLevelId": 0,
//                "SecurityLevelName": null,
//                "FileState": 0,
//                "FileCurrentOperatorId": 0,
//                "FileCreateTime": "2024-05-30 10:26:08",
//                "FileCreateOperator": 6329,
//                "FileModifyTime": "2024-05-30 10:26:08",
//                "FileModifyOperator": 6329,
//                "FileCreateType": 2,
//                "FileArchiveTime": "2024-05-30 10:26:08",
//                "FileArchiveOperator": 0,
//                "IsDeleted": false,
//                "FileDeleteTime": "0001-01-01 00:00:00",
//                "FileDeleteOperator": 0,
//                "FileIsCascadeDelete": false,
//                "FileCipherText": false,
//                "RelativePath": null,
//                "IncId": null,
//                "FileEffectiveTime": "2024-05-30 10:26:08",
//                "FileExpirationTime": "2024-05-30 10:26:08",
//                "ExtInfo": {},
//                "FileType": 2,
//                "FileOfflinePermission": 0,
//                "FilePurgeTime": "0001-01-01 00:00:00",
//                "FilePurgeOperator": 0,
//                "InstanceId": 1,
//                "FileOwnerId": 6329,
//                "currentUserPerm": 0
    }

    @Data
    static
    class FolderInfoResult {
        private Long FolderId;
        private Integer AreaId;
        private Long ParentFolderId;
        private String FolderCode;
        private Integer FolderSortOrder;
        private String FolderName;
        private String FolderPath;
        private String FolderNamePath;
        private Long FolderSize;
        private Long FolderMaxFolderSize;
        private Long FolderAlertSize;
        private Long FolderMaxFileSize;
        private String FolderForbiddenFileExtensions;
        private Integer FolderChildFoldersCount;
        private Integer FolderChildFilesCount;
        private Integer SecurityLevelId;
        private Integer FolderState;
        private Integer FolderLockCount;
        private LocalDateTime FolderCreateTime;
        private Integer FolderCreateOperator;
        private String FolderCreateOperatorName;
        private LocalDateTime FolderModifyTime;
        private Integer FolderModifyOperator;
        private String FolderModifyOperatorName;
        private LocalDateTime FolderArchiveTime;
        private Integer FolderArchiveOperator;
        private Long FolderCurVerId;
        private Long FolderNewestVerId;
        private Integer FolderType;
        private String FolderGuid;
        private Integer FolderOwnerId;
        private Boolean IsDeleted;
        private LocalDateTime FolderDeleteTime;
        private Integer FolderDeleteOperator;
        private String FolderRemark;
        private Boolean FolderIsCascadeDelete;
        /*
         * 示例
         *  {
         *             "FolderId": 227337,
         *             "AreaId": 0,
         *             "ParentFolderId": 227333,
         *             "FolderCode": "",
         *             "FolderSortOrder": 0,
         *             "FolderName": "运维",
         *             "FolderPath": "1\\805\\227333\\227337",
         *             "FolderNamePath": "运维",
         *             "FolderSize": 0,
         *             "FolderMaxFolderSize": 0,
         *             "FolderAlertSize": 0,
         *             "FolderMaxFileSize": 0,
         *             "FolderForbiddenFileExtensions": "",
         *             "FolderChildFoldersCount": 1,
         *             "FolderChildFilesCount": 0,
         *             "SecurityLevelId": 0,
         *             "FolderState": 0,
         *             "FolderLockCount": 0,
         *             "FolderCreateTime": "2024-04-16 15:15:31",
         *             "FolderCreateOperator": 3153,
         *             "FolderCreateOperatorName": "吴佳慧",
         *             "FolderModifyTime": "2024-05-23 14:23:18",
         *             "FolderModifyOperator": 20812,
         *             "FolderModifyOperatorName": "陈一唯",
         *             "FolderArchiveTime": "0001-01-01 00:00:00",
         *             "FolderArchiveOperator": 0,
         *             "FolderCurVerId": 0,
         *             "FolderNewestVerId": 0,
         *             "FolderType": 1,
         *             "FolderGuid": "4cd98592-8822-4056-a28c-abdd99ae6d7e",
         *             "FolderOwnerId": 3153,
         *             "IsDeleted": false,
         *             "FolderDeleteTime": "0001-01-01 00:00:00",
         *             "FolderDeleteOperator": 0,
         *             "FolderRemark": "",
         *             "FolderIsCascadeDelete": false
         *         }
         */
    }

}
